package cn.yangliu.nacos.comm.config.security;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;

import cn.yangliu.nacos.comm.config.security.handler.SecurityAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.util.StringUtils;

/**
 * The interface Web security config.
 *
 * @author 问道于盲
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@ConditionalOnClass(Authentication.class)
@ConditionalOnProperty(prefix = "security.commom", name = "enable", havingValue = "true", matchIfMissing = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * current class instance's member.
     * The security adapter.
     */
    @Autowired(required = false)
    private SecurityAdapter securityAdapter;

    /**
     * current class instance's member.
     * The Properties.
     */
    @Autowired
    private SecurityProperties properties;

    /**
     * inject AuthenticationProvider injected in this class
     */
    @Autowired(required = false)
    private AuthenticationProvider authenticationProvider;

    /**
     * Custom AuthenticationSuccessHandler
     */
    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;

    /**
     * Custom LogoutSuccessHandler
     */
    @Autowired
    private LogoutSuccessHandler logoutSuccessHandler;

    /**
     * Custom AuthenticationEntryPoint
     */
    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    /**
     * Custom AccessDeniedHandler
     */
    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    /**
     * Custom AuthenticationFailureHandler
     */
    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;


    /**
     * Configure.
     *
     * @param web the web
     */
    @Override
    public void configure(WebSecurity web) {
        if (Objects.nonNull(securityAdapter)) {
            securityAdapter.configure(web);
        }
    }

    /**
     * <b>loginProcessingUrl</b> login page = login.html
     * <p>
     * <b>defaultSuccess</b> Url index.html when login success
     * <p>
     * <b>logoutUrl</b> logout page = logout.html
     *
     * @param http the {@link HttpSecurity} to modify
     * @throws Exception if an error occurs
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {


        /**
         * disable csrf
         */
        http.csrf().disable();

        /**
         * session management
         */
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED).maximumSessions(100);

        /**
         * basic login
         */
        http.httpBasic()
                // 未经过认证的用户访问受保护的资源
                .authenticationEntryPoint(authenticationEntryPoint);

        List<String> ignoreUrlList = new ArrayList<>(Arrays.asList(SecurityProperties.DEFAULT_IGNORE_URL.split(",")));
        if (!StringUtils.isEmpty(properties.getIgnoreUrl())) {
            List<String> collect =
                    Arrays.stream(properties.getIgnoreUrl().split(","))
                            .map(String::trim)
                            .filter(url -> !StringUtils.isEmpty(url))
                            .collect(Collectors.toList());
            ignoreUrlList.addAll(collect);
        }


        ignoreUrlList.add(properties.getLoginUrl());
        ignoreUrlList = new ArrayList<>(new HashSet<>(ignoreUrlList));

        String[] ignoreUrls = ignoreUrlList.toArray(new String[]{});

        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry =
				http.authorizeRequests();

        /**
         * 全部放行
         */
        registry = registry.antMatchers(ignoreUrls).permitAll();

        /**
         * 获取当前登录用户的信息接口 完成认证即可访问
         */
        registry = registry.antMatchers(properties.getPrincipalUrl()).authenticated();
        /**
         * 其他配置
         */
        if (Objects.nonNull(securityAdapter)) {
            registry = securityAdapter.configure(registry);
        }
        /**
         * 需要认证
         */
        registry.anyRequest().authenticated();

        String uuid = UUID.randomUUID().toString();
        /**
         * login
         */
        http.formLogin()
                .loginProcessingUrl("/" + uuid)
                .successHandler(authenticationSuccessHandler)
                // 认证失败
                .failureHandler(authenticationFailureHandler)
                .permitAll();

        /**
         * logout
         */
        http.logout()
                .logoutSuccessHandler(logoutSuccessHandler)
                .invalidateHttpSession(true)
                .permitAll();

        if (Objects.nonNull(securityAdapter)) {
            securityAdapter.configure(http);
        }

        /**
         * 限制登陆
         */
        int maximumSessions = properties.getMaximumSessions();
        if (maximumSessions < 1) {
            maximumSessions = 1;
        }

        http.sessionManagement()
                .maximumSessions(maximumSessions)
                .maxSessionsPreventsLogin(properties.isMaxSessionsPreventsLogin());


        /**
         * exceptionHandling
         */
        http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
                // 已经认证的用户访问自己没有权限的资源处理
                .accessDeniedHandler(accessDeniedHandler);
    }


    /**
     * Configure.
     *
     * @param auth the auth
     * @throws Exception the exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        if (Objects.nonNull(authenticationProvider)) {
            auth.authenticationProvider(authenticationProvider)
                    .eraseCredentials(false);
        }
        if (Objects.nonNull(securityAdapter)) {
            securityAdapter.configure(auth);
        }
    }

    /**
     * Authentication manager authentication manager.
     *
     * @return the authentication manager
     * @throws Exception the exception
     */
    @Override
    @Bean
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

}

